<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\VerifyCode;

use Srv\Libs\Boot;
use Srv\Libs\Common\CommFile;
use Srv\Libs\Common\CommString;

final class VerifyCode
{
    private int $width              = 140;      // 验证码的宽
    private int $height             = 40;       // 验证码的高
    private int $fontSize           = 20;       // 字符大小
    private string $codeStr         = '';       // 验证码的内容
    private int $codeNum            = 0;        // 验证码的内容字符数
    private int $noiseNum           = 30;       // 干扰字符次数
    private int $lineNum            = 10;       // 干扰线条次数
    private $image;

    /**
     * @param int $width
     * @param int $height
     * @param string $codeStr
     * __construct
     */
    public function __construct(int $width, int $height, string $codeStr)
    {
        $this->width        = $width;
        $this->height       = $height;
        $this->codeStr      = $codeStr;
        $this->codeNum      = CommString::strLenWord($this->codeStr);
        $this->fontSize     = min(intval(min($this->height, $this->width)*0.65), intval($this->width/$this->codeNum*0.80));
    }

    /**
     * @return string
     * genPng
     */
    public function genPng():string
    {
        $this->genImage();
        ob_start();
        imagepng($this->image);
        $imageStr       = ob_get_contents();
        ob_end_clean();
        imagedestroy($this->image);
        $this->image    = null;
        return $imageStr;
    }

    /**
     * @return string
     * genJpeg
     */
    public function genJpeg():string
    {
        $this->genImage();
        ob_start();
        imagejpeg($this->image);
        $imageStr       = ob_get_contents();
        ob_end_clean();
        imagedestroy($this->image);
        $this->image    = null;
        return $imageStr;
    }

    /**
     * @return string
     * genGif
     */
    public function genGif():string
    {
        $this->genImage();
        ob_start();
        imagegif($this->image);
        $imageStr       = ob_get_contents();
        ob_end_clean();
        imagedestroy($this->image);
        $this->image    = null;
        return $imageStr;
    }

    /**
     * @return void
     * genImage
     */
    private function genImage()
    {
        $this->image    = imagecreate($this->width, $this->height);
        list($red, $green, $blue) = $this->getLightColor();
        $colorBg        = imagecolorallocate($this->image, $red, $green, $blue);
        imagefill($this->image, 0, 0, $colorBg);
        $this->drawNoise();
        $this->drawLine();
        if($this->codeNum > 0){
            $fontPath           = $this->getFontPath();
            if(strlen($fontPath) > 0){
                $left           = intval(($this->width - ($this->codeNum * $this->fontSize)) / 2 * mt_rand(60, 75)*0.01);
                $top            = intval($this->fontSize + ($this->height - $this->fontSize) / 2);
                $colorCharLast  = [];
                for($i = 0; $i < $this->codeNum; $i++){
                    list($red, $green, $blue) = $this->getDeepColor();
                    $colorChar      = imagecolorallocate($this->image, $red, $green, $blue);
                    if($colorChar !== false){
                        $colorCharLast[] = $colorChar;
                    }else{
                        $colorChar  = $colorCharLast[mt_rand(0, count($colorCharLast)-1)]??false;
                    }
                    if($colorChar === false) continue;
                    $chatStr        = CommString::strConvert(CommString::strSubWord($this->codeStr, $i, 1), 'html-entities', 'UTF-8');
                    imagettftext($this->image, $this->fontSize, mt_rand(-30, 30), $left, $top, $colorChar, $fontPath, $chatStr);
                    $left           += mt_rand($this->fontSize, intval($this->fontSize * 1.2));
                }
            }
        }
    }

    /**
     * @return void
     * drawLine
     */
    private function drawLine():void
    {
        if($this->lineNum < 1) return;
        $width2         = intval($this->width/2);
        $height2        = intval($this->height/4);
        $colorLineLast  = false;
        for ($i = 0; $i < $this->lineNum; $i++){
            $colorLine  = imagecolorallocate($this->image, mt_rand(100, 255), mt_rand(100, 255), mt_rand(100, 255));
            if($colorLine !== false) $colorLineLast = $colorLine;
            if($colorLineLast === false) continue;
            if(mt_rand(0, 1)){
                $Xa     = mt_rand(0, $width2);
                $Ya     = mt_rand(0, $this->height);
                $Xb     = mt_rand($width2, $this->width);
                $Yb     = mt_rand(0, $this->height);
            }else{
                $Xa     = mt_rand(0, $this->width);
                $Ya     = mt_rand(0, $height2);
                $Xb     = mt_rand(0, $this->width);
                $Yb     = mt_rand($height2, $this->height);
            }
            imagesetthickness($this->image, mt_rand(1, 3));
            imageline($this->image, $Xa, $Ya, $Xb, $Yb, $colorLineLast);
        }
    }

    /**
     * @return void
     * drawNoise
     */
    private function drawNoise():void
    {
        if($this->noiseNum < 1) return;
        $noiseStrLength = 32;
        $noiseStr       = CommString::genRandStr($noiseStrLength);
        $colorNoiseLast = false;
        for($i = 0; $i < $this->noiseNum; $i++){
            list($red, $green, $blue) = $this->getLightColor();
            $colorNoise = imagecolorallocate($this->image, $red, $green, $blue);
            if($colorNoise !== false) $colorNoiseLast = $colorNoise;
            if($colorNoiseLast === false) continue;
            $num        = mt_rand(3, 8);
            for($j = 0; $j < $num; $j++){
                imagestring($this->image, 5, mt_rand(-10, $this->width),  mt_rand(-10, $this->height), $noiseStr[mt_rand(0, $noiseStrLength-1)], $colorNoiseLast);
            }
        }
    }

    /**
     * @return string
     * getFontPath
     */
    private function getFontPath():string
    {
        $fontList   = [];
        $fontDir    = dirname(__FILE__).'/font/';
        if(!CommFile::getFileList($fontDir, $fontList) || count($fontList) < 1) return '';
        $fontPath   = $fontList[rand(0, count($fontList)-1)];
        if(!IS_PHAR) return $fontPath;
        // 兼容phar
        $fontPathNew    = Boot::getTmpPath().'cache_font_'.md5($fontPath);
        if(file_exists($fontPathNew) && filesize($fontPathNew) > 8) return $fontPathNew;
        $content        = '';
        if(CommFile::getContent($fontPath, $content)) CommFile::writeFileString($fontPathNew, $content);
        return $fontPathNew;
    }

    /**
     * @return array
     * getLightColor
     */
    private function getLightColor():array
    {
        return [
            200 + mt_rand(1, 55),
            200 + mt_rand(1, 55),
            200 + mt_rand(1, 55),
        ];
    }

    /**
     * @return array
     * getDeepColor
     */
    private function getDeepColor():array
    {
        return [
            10 + mt_rand(1, 155),
            10 + mt_rand(1, 155),
            10 + mt_rand(1, 155),
        ];
    }
}